
package com.tools.cleanmaster.manager;

import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.database.Cursor;
import android.os.Build;
import android.provider.MediaStore;

import com.tools.cleanmaster.CleanApplication;
import com.tools.cleanmaster.bean.ApkFileInfo;
import com.tools.cleanmaster.controller.FileScanner;
import com.tools.cleanmaster.model.item.ApkFileItem;
import com.tools.cleanmaster.utils.FeatureConfig;
import com.tools.cleanmaster.utils.LogHelper;
import com.tools.cleanmaster.utils.PackageUtils;
import com.tools.cleanmaster.utils.PackageUtils.PkgInfo;
import com.tools.cleanmaster.utils.TrashUtils;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Set;

/**
 * 管理Apk文件的类：单例
 * 
 * @author ouyang
 */
public class ApkFileManager {
    private static final String TAG = "TrashApkFileManager";

    private static ApkFileManager sInstance = null;

    private static Object mLocker = new Object();

    private ApkFileManager() {
        mSdCardRootDirs = TrashUtils.findSDPaths(CleanApplication.getInstance());
    }

    public static ApkFileManager getInstance() {
        synchronized (mLocker) {
            if (sInstance == null) {
                sInstance = new ApkFileManager();
            }
        }
        return sInstance;
    }

    /**
     * APK信息索引，Key为ApkFile的路径
     */
    private HashMap<String, ApkFileInfo> mApkFileInfos = new HashMap<String, ApkFileInfo>();

    private String[] mSdCardRootDirs = null;

    /**
     * 根据pkgName和versionName获取包路径。 会先在缓存中找，如果找不到，则扫描；
     * 
     * @param pkgName
     * @param versionName
     * @return
     */
    public ArrayList<String> queryApkFiles(String pkgName, String versionName) {
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.i(TAG, "queryApkFiles pkgName=" + pkgName + ",versionName=" + versionName);
        }
        if (pkgName == null || versionName == null) {
            return null;
        }
        // 1.find from cache. 2.find from mediaStore or sdcard
        ArrayList<String> paths = findApkFilesFromCache(pkgName, versionName);
        if (!paths.isEmpty()) {
            return paths;
        }
        // find from mediaStore or sdcard
        scanApkFiles();
        return findApkFilesFromCache(pkgName, versionName);
    }

    private void scanApkFiles() {
        long startTime = System.currentTimeMillis();
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.i(TAG, "scanApkFiles start....");
        }
        ArrayList<String> apkPaths = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            apkPaths = scanApkFilesByMediaStore();
        } else {
            apkPaths = scanApkFilesByRoots();
        }
        if (apkPaths != null && !apkPaths.isEmpty()) {
            for (String path : apkPaths) {
                ApkFileInfo info = mApkFileInfos.get(path);
                if (info != null) {
                    File file = new File(path);
                    if (info.size != file.length()) {// 文件有修改
                        ApkFileInfo newInfo = parseApkInfo(path);
                        if (newInfo != null) {
                            putApkInfo(newInfo);
                        } else {
                            mApkFileInfos.remove(path);
                        }
                    }
                } else {
                    ApkFileInfo newInfo = parseApkInfo(path);
                    if (newInfo != null) {
                        putApkInfo(newInfo);
                    }
                }
            }
            // 没有的文件也更新
            Set<String> keySet = mApkFileInfos.keySet();
            ArrayList<String> removedPaths = new ArrayList<String>();
            for (String path : keySet) {
                if (!apkPaths.contains(path)) {
                    File file = new File(path);
                    if (!file.exists()) {
                        removedPaths.add(path);
                    } else {
                        ApkFileInfo info = mApkFileInfos.get(path);
                        if (info.size != file.length()) {// 文件有修改，更新它
                            ApkFileInfo newInfo = parseApkInfo(path);
                            if (newInfo != null) {
                                putApkInfo(newInfo);
                            } else {
                                removedPaths.add(path);
                            }
                        }
                        // MediaFilesScanner.startScanFiles(path, false);//
                        // scan此MediaStore
                    }
                }
            }
            // 删除已经不存在的文件
            if (!removedPaths.isEmpty()) {
                for (String path : removedPaths) {
                    mApkFileInfos.remove(path);
                }
            }
        }
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.i(TAG, "scanApkFiles end....time ="
                    + (System.currentTimeMillis() - startTime));
        }
    }

    private ApkFileInfo parseApkInfo(String path) {
        if (path == null) {
            return null;
        }
        File file = new File(path);
        if (!file.exists() || !file.isFile()) {
            return null;
        }
        ApkFileInfo info = new ApkFileInfo();
        info.filePath = path;
        info.apkCreateTime = file.lastModified();
        info.size = file.length();
        PkgInfo apkPkgInfo = PackageUtils.getArchievePkgInfo(CleanApplication.getInstance(), path);
        if (apkPkgInfo == null) {
            info.flag = ApkFileItem.FLAG_BROKEN;
        } else {
            info.pkgName = apkPkgInfo.pkgName;
            info.appName = apkPkgInfo.label;
            info.versionCode = apkPkgInfo.versionCode;
            info.versionName = apkPkgInfo.versionName;
        }
        return info;
    }

    private ArrayList<String> scanApkFilesByRoots() {
        ArrayList<String> paths = new ArrayList<String>();
        String[] posFix = new String[] {
            ".apk"
        };
        for (String rootPath : mSdCardRootDirs) {
            File file = new File(rootPath);
            if (file.exists()) {
                paths.addAll(TrashUtils.getFilePaths(file, 0, TrashUtils.DEPTH_MAX, posFix, false));
            }
        }
        return paths;
    }

    @SuppressLint("NewApi")
    private ArrayList<String> scanApkFilesByMediaStore() {
        ArrayList<String> apkPaths = new ArrayList<String>();
        Cursor cursor = null;
        try {
            ContentResolver contentResolver = CleanApplication.getInstance().getContentResolver();
            cursor = contentResolver.query(
                    MediaStore.Files.getContentUri(FileScanner.DB_NAME_EXTERNAL), new String[] {
                        MediaStore.Files.FileColumns.DATA
                    }, FileScanner.SQL_SECLECTION_DATA_POSTFIX, new String[] {
                        ".apk"
                    }, null);
            if (cursor == null) {
                return apkPaths;
            }
            int count = cursor.getCount();
            if (FeatureConfig.DEBUG_LOG) {
                LogHelper.v(TAG, "doScan, scanApkFilesByMediaStore cursor count=" + count);
            }
            if (count == 0) {
                return apkPaths;
            }
            while (cursor.moveToNext()) {
                String path = cursor.getString(0);
                File file = new File(path);
                if (file.exists()) {
                    boolean isSkippedFolder = false;
                    for (String root : mSdCardRootDirs) {
                        for (String p : FileScanner.APK_PATH_SKIPPED) {
                            if (path.startsWith(new File(root, p).getAbsolutePath())) {
                                isSkippedFolder = true;
                                break;
                            }
                        }
                        if (isSkippedFolder) {
                            break;
                        }
                    }
                    if (!isSkippedFolder) {
                        apkPaths.add(path);
                    }
                }
            }
        } catch (Exception e) {
            if (FeatureConfig.DEBUG_LOG) {
                LogHelper.e(TAG, "scanApkFilesByMediaStore", e);
            }
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        return apkPaths;
    }

    private ArrayList<String> findApkFilesFromCache(String pkgName, String versionName) {
        ArrayList<String> paths = new ArrayList<String>();
        Collection<ApkFileInfo> infos = mApkFileInfos.values();
        if (infos != null && !infos.isEmpty()) {
            for (ApkFileInfo apkInfo : infos) {
                if (pkgName.equals(apkInfo.pkgName) && versionName.equals(apkInfo.versionName)
                        && new File(apkInfo.filePath).exists()) {
                    if (FeatureConfig.DEBUG_LOG) {
                        LogHelper.i(TAG, "findApkFilesFromCache pkgName=" + pkgName
                                + ",versionName=" + versionName + ",apkInfo.filePath="
                                + apkInfo.filePath);
                    }
                    paths.add(apkInfo.filePath);
                }
            }
        }
        return paths;
    }

    /**
     * 通过path获取ApkFileInfo
     * 
     * @param path
     * @return
     */
    public ApkFileInfo getApkInfo(String path) {
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.i(TAG, "getApkInfo path=" + path);
        }
        if (path == null) {
            return null;
        }
        File file = new File(path);
        if (!file.exists() || !file.isFile()) {
            return null;
        }
        ApkFileInfo apkFileInfo = mApkFileInfos.get(path);
        if (apkFileInfo != null) {
            if (apkFileInfo.size == file.length()) {
                if (FeatureConfig.DEBUG_LOG) {
                    LogHelper.i(TAG, "getApkInfo cache hit! path=" + path);
                }
                return apkFileInfo;
            }
        }
        // 缓存中不存在或不一致，则重新parseAPK
        ApkFileInfo parseApkInfo = parseApkInfo(path);
        if (parseApkInfo != null) {
            if (FeatureConfig.DEBUG_LOG) {
                LogHelper.i(TAG, "getApkInfo parse apk! path=" + path);
            }
            putApkInfo(parseApkInfo);
        }
        return parseApkInfo;
    }

    /**
     * @param info
     */
    private void putApkInfo(ApkFileInfo info) {
        if (info != null && info.filePath != null) {
            mApkFileInfos.put(info.filePath, info);
        }
    }
}
